package com.springboot.ww.platform.pd.http;

import lombok.Data;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 拼多多 服务器代理
 */
public class PdHttpProxy {
    // 依次是代理地址，代理端口号，用户密码
    private static int proxyPort = 36535;
    private static String socksUserName = "aAjqAlcu7B";
    private static String socksPassword = "XyqDNjz5g0";

    private static final int MAX_TIMEOUT = 30000;
    private static final Map<String, ConnectEntity> CONNECT_ENTITY_MAP = new HashMap<>();

    static {
        //用户名和密码验证
        Authenticator.setDefault(new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                PasswordAuthentication p = new PasswordAuthentication(socksUserName, socksPassword.toCharArray());
                return p;
            }
        });
    }


    public static ResponseMsg proxyWithRequest(String proxyHost, HttpRequestBase request) {
        try {
            ConnectEntity connectEntity = getConnectEntity(proxyHost);
            CloseableHttpResponse response = connectEntity.getHttpClient().execute(request, connectEntity.getHttpClientContext());
            int status = response.getStatusLine().getStatusCode();
            String result = new String(EntityUtils.toByteArray(response.getEntity()), Charset.defaultCharset());
            return new ResponseMsg(status, result);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (Objects.nonNull(request)) {
                request.releaseConnection();
            }
        }
    }

    private static ConnectEntity getConnectEntity(String proxyHost) {
        ConnectEntity connectEntity = CONNECT_ENTITY_MAP.get(proxyHost);
        if(connectEntity == null){
            synchronized (PdHttpProxy.class){
                connectEntity = CONNECT_ENTITY_MAP.get(proxyHost);
                if(connectEntity == null){
                    CloseableHttpClient httpClient = getHttpClient();
                    HttpClientContext httpClientContext = HttpClientContext.create();
                    InetSocketAddress socksaddr = new InetSocketAddress(proxyHost, proxyPort);
                    httpClientContext.setAttribute("socks.address", socksaddr);
                    connectEntity = new ConnectEntity(httpClient,httpClientContext);
                    CONNECT_ENTITY_MAP.put(proxyHost,connectEntity);
                }
            }
        }
        return connectEntity;
    }

    private static CloseableHttpClient getHttpClient() {
        RequestConfig.Builder configBuilder = RequestConfig.custom();
        // 设置连接超时
        configBuilder.setConnectTimeout(MAX_TIMEOUT);
        // 设置读取超时
        configBuilder.setSocketTimeout(MAX_TIMEOUT);
        // 设置从连接池获取连接实例的超时
        configBuilder.setConnectionRequestTimeout(MAX_TIMEOUT);
        configBuilder.setStaleConnectionCheckEnabled(true);
        RequestConfig requestConfig = configBuilder.build();

        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", new MyConnectionSocketFactory())
                .register("https", new MySSLConnectionSocketFactory(SSLContexts.createSystemDefault())).build();
        PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(socketFactoryRegistry, new FakeDnsResolver());
        // 设置连接池大小
        connMgr.setMaxTotal(10);
        connMgr.setDefaultMaxPerRoute(connMgr.getMaxTotal());

        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connMgr).setDefaultRequestConfig(requestConfig).build();
        return httpClient;
    }

    public static byte[] proxyByteWithRequest(String proxyHost, HttpRequestBase request) {
        try {
            ConnectEntity connectEntity = getConnectEntity(proxyHost);
            CloseableHttpResponse response = connectEntity.getHttpClient().execute(request, connectEntity.getHttpClientContext());
            return EntityUtils.toByteArray(response.getEntity());
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (Objects.nonNull(request)) {
                request.releaseConnection();
            }
        }
    }

    static class FakeDnsResolver implements DnsResolver {
        @Override
        public InetAddress[] resolve(String host) throws UnknownHostException {
            // Return some fake DNS record for every request, we won't be using it
            return new InetAddress[]{InetAddress.getByAddress(new byte[]{1, 1, 1, 1})};
        }
    }

    static class MyConnectionSocketFactory extends PlainConnectionSocketFactory {
        @Override
        public Socket createSocket(final HttpContext context) throws IOException {
            InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
            Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
            return new Socket(proxy);
        }

        @Override
        public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,
                                    InetSocketAddress localAddress, HttpContext context) throws IOException {
            // Convert address to unresolved
            InetSocketAddress unresolvedRemote = InetSocketAddress
                    .createUnresolved(host.getHostName(), remoteAddress.getPort());
            return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context);
        }
    }

    static class MySSLConnectionSocketFactory extends SSLConnectionSocketFactory {

        public MySSLConnectionSocketFactory(final SSLContext sslContext) {
            // You may need this verifier if target site's certificate is not secure
            super(sslContext, ALLOW_ALL_HOSTNAME_VERIFIER);
        }

        @Override
        public Socket createSocket(final HttpContext context) throws IOException {
            InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
            Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
            return new Socket(proxy);
        }

        @Override
        public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,
                                    InetSocketAddress localAddress, HttpContext context) throws IOException {
            // Convert address to unresolved
            InetSocketAddress unresolvedRemote = InetSocketAddress
                    .createUnresolved(host.getHostName(), remoteAddress.getPort());
            return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context);
        }
    }

    @Data
    static class ConnectEntity{
        private CloseableHttpClient httpClient;
        private HttpClientContext httpClientContext;

        public ConnectEntity(CloseableHttpClient httpClient, HttpClientContext httpClientContext) {
            this.httpClient = httpClient;
            this.httpClientContext = httpClientContext;
        }
    }
}